﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Compiler.TableOfSymbols.Types;
using Compiler.Unification.Substitutions;
using Compiler.Unification.Constraints;

namespace Compiler.TableOfSymbols.Concepts
{
    public class ConceptDefinition : SymbolTableData
    {
        /// <summary>
        /// Вспомогательный класс для идентификации типовых параметров
        /// концепта в порядке их объявления в заголовке концепта
        /// </summary>
        class ConceptTypeParameter : IComparable<ConceptTypeParameter>
        {
            private int index;
            private string name;

            public string Name
            { 
                get { return name; } 
            }

            public ConceptTypeParameter(int index, string name)
            {
                System.Diagnostics.Debug.Assert(index >= 0,
                    String.Format("index must be >= 0"));
                System.Diagnostics.Debug.Assert((name != null) && (name != ""),
                 String.Format("name can not be empty"));
                this.index = index;
                this.name = name;
            }

            public int CompareTo(ConceptTypeParameter other)
            {
                return this.index.CompareTo(other.index);
            }

            public override string ToString()
            {
                return String.Format("{0}: {1}", index, name);
            }
        }

        private string name;
        private SortedList<ConceptTypeParameter, BaseType> typeParameters;
        private SortedDictionary<string, BaseType> associatedTypes;
        private HashSet<BaseTypeConstraint> constraints;

        public string Name
        { 
            get { return name; } 
        }

        public HashSet<string> BadTypeParams
        {
            get 
            {
                HashSet<string> result = new HashSet<string>();
                var typeParams = typeParameters.Values;
                foreach (KeyValuePair<ConceptTypeParameter, BaseType> pair in typeParameters)
                    if (pair.Value.IsTypeValue)
                        result.Add(pair.Key.Name);
                return result;
            }
        }
        public HashSet<string> ConstrainedTypeParams
        {
            get
            {
                HashSet<string> result = new HashSet<string>();
                var typeParams = typeParameters.Values;
                foreach (KeyValuePair<ConceptTypeParameter, BaseType> pair in typeParameters)
                    if (pair.Value.IsTypeVariable)
                    {
                        TypeVariable tv = pair.Value as TypeVariable;
                        if (!tv.MaxLowerType.IsBottomType ||!tv.MinUpperType.IsTopType)
                            result.Add(pair.Key.Name);
                    }
                return result;
            }
        }
        public bool HasEqualTypeParamsOrAssociatedTypes
        {
            get 
            {
                HashSet<BaseType> types = new HashSet<BaseType>();
                foreach (KeyValuePair<ConceptTypeParameter, BaseType> pair in typeParameters)
                    if (types.Contains(pair.Value))
                        return true;
                foreach (KeyValuePair<string, BaseType> pair in associatedTypes)
                    if (types.Contains(pair.Value))
                        return true;
                return false;
            }
        }

        public ConceptDefinition(string name) : base()
        {
            if ((name == null) || (name == ""))
                throw new ArgumentOutOfRangeException("name can not be empty");
            this.name = name;
            this.typeParameters = new SortedList<ConceptTypeParameter, BaseType>();
            this.associatedTypes = new SortedDictionary<string, BaseType>();
            this.constraints = null;
        }

        public TypeVariable AddNewTypeParameter(string name)
        {
            if ((name == null) || (name == ""))
                throw new ArgumentOutOfRangeException("name can not be empty");
            TypeVariable typeVar = new TypeVariable(name);
            typeParameters.Add(new ConceptTypeParameter(typeParameters.Count, name), typeVar);
            return typeVar;
        }

        public TypeVariable AddNewAssociatedType(string name)
        {
            if ((name == null) || (name == ""))
                throw new ArgumentOutOfRangeException("name can not be empty");
            TypeVariable typeVar = new TypeVariable(name);
            associatedTypes.Add(name, typeVar);
            return typeVar;
        }

        public void ApplySubstitution(Substitution subs)
        {
            var keysParam = new LinkedList<ConceptTypeParameter>(this.typeParameters.Keys);
            foreach (ConceptTypeParameter param in keysParam)
                this.typeParameters[param] = subs.Apply(this.typeParameters[param]);
            var keysAssoc = new LinkedList<string>(this.associatedTypes.Keys);
            foreach (string assoc in keysAssoc)
                this.associatedTypes[assoc] = subs.Apply(this.associatedTypes[assoc]);
        }

        public void SetConstraints(HashSet<BaseTypeConstraint> constraints)
        {
            if (constraints == null)
                throw new ArgumentNullException("constraints can not be null");
            this.constraints = constraints;
        }

        protected override void _SetCategory()
        {
            this.category = SymbolTableDataCategory.CONCEPT_DEF;
        }

        public string MakeDescription(NETTypesBijectiveMap typesMap)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("Концепт " + this.name);
            sb.AppendLine("---------------------------");
            sb.AppendLine("Параметры концепта:");
            foreach (ConceptTypeParameter param in this.typeParameters.Keys)
                sb.AppendLine(String.Format("    {0}: {1};", param.Name,
                    typesMap.GetFullDescription(this.typeParameters[param])));
            sb.AppendLine("Ассоциированные типы:");
            foreach (string assoc in this.associatedTypes.Keys)
                sb.AppendLine(String.Format("    {0}: {1};", assoc, 
                    typesMap.GetFullDescription(this.associatedTypes[assoc])));
            if (this.constraints != null)
            {
                sb.AppendLine("Ограничения:");
                sb.Append("{");
                foreach (BaseTypeConstraint constraint in constraints)
                    sb.Append(constraint.MakeDescription(typesMap) + ";");
                sb.AppendLine("}");
            }
            return sb.ToString();
        }
    }
}
